<?php

namespace spider;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use think\Exception;

/**
 * 采集网站：https://www.teepublic.com/
 */
class Teepublic
{
    protected static $host = "https://www.teepublic.com";

    public static function spider($params)
    {
        if (str_contains($params['get_url'], 'query=')) {
            $params['page'] = $params['page'] ?? 1; //默认第一页
            $ret = []; //抓取到的数据集合
            self::spiderSearchList($params, $ret);
            $ret = [
                'list' => $ret,
                'count' => count($ret),
//                'spider_type' => 'list',
            ];

            $issetNameTempArr = [];
            foreach ($ret['list'] as &$row) {
                $title = str_replace(["/", "*", "\\", "?", "\"", "<", ">", "|", ":", ","], '', $row['name']);
                $tempTitle = strtolower($title);//win下不区分大小写，linux区分
                // 本地保存路径
                $path = "/shop/images/teepublic/$title.jpg";

                //判断是否需要重命名
                if (in_array($tempTitle, $issetNameTempArr) || file_exists(ROOT_PATH . "public" . $path)) {
                    $title .= "--" . uniqid();
                    $path = "/shop/images/teepublic/$title.jpg";
                }
                $issetNameTempArr[] = $tempTitle;
                $row['local_img_url'] = $path; //记录本地地址

                $temp = $row;
                $temp['platform'] = 'teepublic'; //记录平台

                self::addSaveImagesQueue($temp);
            }
        }
        else {
            //单个链接抓取
            $ret = self::spiderSingle($params);
//            $ret['spider_type'] = 'single';
            if (empty($ret['shopInfo'])) return false;

            //加入保存图片到本地队列
            $title = str_replace(["/", "*", "\\", "?", "\"", "<", ">", "|", ":", ","], '', $ret['shopInfo']['name']);
            $tempTitle = $title . "--" . uniqid();
            $path = "/shop/images/teepublic/$tempTitle.jpg";

            $pathArr = [];
            // 本地保存路径
            foreach ($ret['list'] as &$row) {
                $temp = $row;
                $temp['platform'] = 'teepublic'; //记录平台
                $tempImage = explode("_____", $row['images']);
                $pathTempArr = [];
                foreach ($tempImage as $key => $image) {
                    $temp['images'] = $image;
                    //加入保存图片到本地队列
                    if ($temp['is_saveimg']) {
                        $tempTitle = $title . "--" . uniqid();
                        $path = "/shop/images/teepublic/$tempTitle.jpg";
                        $pathArr[$key] = $path;
                        $temp['local_img_url'] = $path;

                        self::addSaveImagesQueue($temp);
                    } else {
                        $path = $pathArr[$key] ?? $path;
                    }
                    $pathTempArr[] = $path;
                }
                $row['local_img_url'] = implode(',', $pathTempArr); //记录本地地址

                unset($row['is_saveimg']);
            }
        }
        return $ret;
    }
    private static function addSaveImagesQueue($params)
    {
        addSaveImagesQueue($params);
    }

    /**
     * 采集搜索列表
     * @param $params
     * @param $data
     * @return void
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    private static function spiderSearchList($params, &$data)
    {
        //查询页数大于1时，每页暂停1秒，以免被封，如果后续有IP池可不用
        if ($params['page'] > 1) sleep(1);

        //请求URL的数据
        $url = $params['get_url'] . "&page=" . $params['page'];
        echo $url . "\n";

        try {
            $html = self::request($url);
            /*TODO：调试时写入文件，查看页面代码用*/
//            file_put_contents(SPIDER_HTML_PATH . "/teepublic{$params['page']}.html", $html);
//            $html = file_get_contents(SPIDER_HTML_PATH . "/teepublic{$params['page']}.html");

            $namePattern = '/<div[^>]*class="[^"]*tp-design-tile jsDesignContainer[^"]*"[^>]*>(.*?)\s+<\/p>\s+<\/div>\s+<\/div>/s';
            preg_match_all($namePattern, $html, $nameMatches);

            // 定义标题正则表达式
            $titlePattern = "/<h2 class='tp-design-tile__title ellip'>(.*?)<\/h2>/";
            // 定义价格正则表达式
            $pricePattern = "/<span class='tp-design-tile__price--regular'>(.*?)<\/span>/s";
            // 定义图片正则表达式
            $imagePattern = '/<img class="tp-design-tile__image".*?src="(.*?)"/s';
            // 定义链接正则表达式
            $linkPattern = '/<a[^>]*href="([^"]*)"[^>]*class="[^"]*tp-design-image__preview_link[^"]*"[^>]*>/';

            foreach ($nameMatches[1] as $row) {
                preg_match($titlePattern, $row, $titleMatches);
                $productName = isset($titleMatches[1]) ? trim($titleMatches[1]) : '';

                preg_match($pricePattern, $row, $priceMatches);
                $productPrice = isset($priceMatches[1]) ? trim($priceMatches[1]) : '';

                preg_match($imagePattern, $row, $imageMatches);
                $productImage = isset($imageMatches[1]) ? trim($imageMatches[1]) : '';

                preg_match($linkPattern, $row, $linkMatches);
                $productLink = isset($linkMatches[1]) ? trim($linkMatches[1]) : '';

                $tempNewData = [
                    'shop_uniqid' => \fast\Random::alnum(32),
                    'task_id' => (int)$params['task_id'],
                    'name' => $productName,
                    'price' => $productPrice,
                    'images' => $productImage,
                    'url' => self::$host . $productLink,
                ];
                $data[] = $tempNewData;

                //将单个商品放入队列抓取
                $tempNewData['get_url'] = $tempNewData['url'];
                addJob('app\\jobs\\Task', $tempNewData, 'task');
            }
            $nextPage = self::getNextPageIndex($html);
            if ($nextPage) {
                //调用抓取
                $params['page'] = $nextPage;
//                $params['get_url'] = $params['get_url'] . "&page=" . $nextPage;
                addJob('app\\jobs\\Task', $params, 'task');
//                self::spiderSearchList($params, $data);
            }

            echo 1;
            return;
        } catch (Exception $e) {
            echo "Error: " . $e->getMessage();
        }
    }

    /**
     * 获取下一页的index
     * @param $html
     * @return false|string
     */
    private static function getNextPageIndex($html)
    {
        $pagePattern = '/<a[^>]*rel="next"[^>]*>(.*?)<\/a>/s';
        preg_match($pagePattern, $html, $pageMatches);
        if (empty($pageMatches[1]) || is_array($pageMatches[1])) return false;  //如果是数组，停止往下，避免程序崩溃

        //TODO:测试调用前几页，用于测试，测试完成需注释
//        if ($pageMatches[1] > 2) return false;

        return $pageMatches[1] ?? false;
    }

    //采集单个商品内容
    private static function spiderSingle($params)
    {
//        sleep(1);
        $productId = self::getProductId($params['get_url']); //获取商品ID
        $currTime = time();
        try {
            $html = self::request($params['get_url']);
            echo $params['get_url'] . "\n";
            /*TODO：调试时写入文件，查看代码用*/
//            file_put_contents(SPIDER_HTML_PATH . "/teepublic{$productId}.html", $html);
//            $html = file_get_contents(SPIDER_HTML_PATH . "/teepublic{$productId}.html");

            //正则获取页面数据
            list($productName, $pageTitle, $metaDescription, $storeUrl, $shopDescription, $productJson) = self::getPageDataByPreg($html);

            $productJson = json_decode($productJson, true);
            if (empty($productJson)) return false;
            //商品参数
            $prices = self::handleProductsInfo($productJson['CanvasOptions'], 'prices');
            $material = self::handleProductsInfo($productJson['CanvasOptions'], 'material');
            $shopAttrFieldArr = self::handleProductsInfo($productJson['CanvasOptions'], 'attrIdsOrder');//商品属性字段

            $products = $productJson['CanvasOptions']['products'];
            //将无用的元素删除
            unset($products['attrIdsOrder'], $products['attrs'], $products['prices_keys'], $products['prices'], $products['descriptions'], $products['tooltips'], $products['colors']);

            $ImagesList = self::getProductImages($productId, $params['get_url']); //获取产品对应的图片
            $newList = [];

            foreach ($ImagesList as $row) {
                $imageUrl = $row['images'];
                $flag = 1;
                foreach ($row['product_ids'] as $ids) {
                    if (empty($products[$ids])) {
                        echo "$ids<br>";
                        continue;
                    }

                    $priceIndex = $products[$ids][0]; //价格下标
                    $tempNewList = [
                        'task_id' => $params['task_id'],
                        'shop_uniqid' => $params['shop_uniqid'],
                        'images' => $imageUrl,
                        'price' => $prices[$priceIndex],
                        'material' => $material[$ids],
                        'updatetime' => $currTime,
                        'createtime' => $currTime,
                    ];

                    $infosIndex = $products[$ids][1]; //款式下标
                    foreach ($infosIndex as $key => $index) {
                        $field = $shopAttrFieldArr[$key];
                        $fieldData = self::handleProductsInfo($productJson['CanvasOptions'], $field);
                        $tempNewList[$field] = $fieldData[$index] ?? null;
                    }
                    $tempNewList['is_saveimg'] = $flag;
                    $flag = 0;
                    $newList[] = $tempNewList;

//                  /*TODO：调试时用*/
//                    if (count($newList) >= 10) break 2;
                }

            }

            $shopInfo = [
                'name' => $productName,
                'page_title' => $pageTitle,
                'meta_description' => $metaDescription,
                'store_url' => self::$host . $storeUrl,
                'shop_description' => $shopDescription,
                "shop_uniqid" => $params['shop_uniqid'],
            ];

            echo 2;
            return ['list' => $newList, 'shopInfo' => $shopInfo];
        } catch (Exception $e) {
            echo "Error: " . $e->getMessage();
        }
        return ['list' => [], 'shopInfo' => []];
    }
    private static function spiderSingle_bak($params)
    {
//        sleep(1);
        $productId = self::getProductId($params['get_url']); //获取商品ID
        $currTime = time();
        try {
//            $html = self::request($params['get_url']);
//            /*TODO：调试时写入文件，查看代码用*/
//            file_put_contents(SPIDER_HTML_PATH . "/teepublic{$productId}.html", $html);
            $html = file_get_contents(SPIDER_HTML_PATH . "/teepublic{$productId}.html");

            // 定义标题正则表达式
            $titlePattern = "/<h1[^>]*class='h__h1--sm h--no-s-b'[^>]*>(.*?)<\/h1>/";
            preg_match($titlePattern, $html, $titleMatches);
            $productName = isset($titleMatches[1]) ? trim($titleMatches[1]) : '';

            // 定义商品json正则表达式
            $jsonPattern = "/TeePublic\['ProductOptions'\]\s*=\s*(\{.*?\});/s";
            preg_match($jsonPattern, $html, $jsonMatches);
            $productJson = isset($jsonMatches[1]) ? trim($jsonMatches[1]) : '';
            $productJson = json_decode($productJson, true);
            if (empty($productJson)) return false;

            // 定义title标签正则表达式
            $titlePattern = "/<title[^>]*>(.*?)<\/title>/";
            preg_match($titlePattern, $html, $titleMatches);
            $pageTitle = isset($titleMatches[1]) ? trim($titleMatches[1]) : '';

            // 定义meta正则表达式
            $pattern = "/<meta\s+content='([^']*)'\s+name='description'\s*\/?>/i";
            preg_match($pattern, $html, $matches);
            $metaDescription = isset($matches[1]) ? trim($matches[1]) : '';

            // 定义店铺链接正则表达式
            $pattern = '/<a[^>]*href="([^"]*)"[^>]*class="[^"]*link m-also-available-products--more-by-artist btn c-link__button tp-btn--medium link--default[^"]*"[^>]*>/';
            preg_match($pattern, $html, $matches);
            $storeUrl = isset($matches[1]) ? trim($matches[1]) : '';

            // 定义标题正则表达式
            $pattern = '/"description": "(.*?)",/s';
            preg_match($pattern, $html, $matches);
            $shopDescription = isset($matches[1]) ? trim($matches[1]) : '';

            //商品参数
            $colors = self::handleProductsInfo($productJson['CanvasOptions'], 'color');
            $gender = self::handleProductsInfo($productJson['CanvasOptions'], 'gender');
            $styles = self::handleProductsInfo($productJson['CanvasOptions'], 'style');
            $size = self::handleProductsInfo($productJson['CanvasOptions'], 'size');
            $prices = self::handleProductsInfo($productJson['CanvasOptions'], 'prices');
            $material = self::handleProductsInfo($productJson['CanvasOptions'], 'material');
            $products = $productJson['CanvasOptions']['products'];

            //将无用的元素删除
            unset($products['attrIdsOrder'], $products['attrs'], $products['prices_keys'], $products['prices'], $products['descriptions'], $products['tooltips'], $products['colors']);

            $ImagesList = self::getProductImages($productId); //获取产品对应的图片
            $newList = [];

            foreach ($ImagesList as $row) {
                $imageUrl = $row['images'];
                $flag = 1;
                foreach ($row['product_ids'] as $ids) {
                    if (empty($products[$ids])) continue;

                    $priceIndex = $products[$ids][0]; //价格下标
                    $infosIndex = $products[$ids][1]; //款式下标
                    //组成新数组
                    $newList[] = [
                        'task_id' => $params['task_id'],
                        'shop_uniqid' => $params['shop_uniqid'],
                        'color' => $colors[$infosIndex[3]],
                        'gender' => $gender[$infosIndex[0]] ?? null,
                        'style' => $styles[$infosIndex[1]],
                        'size' => $size[$infosIndex[2]],
                        'price' => $prices[$priceIndex],
                        'images' => $imageUrl,
                        'material' => $material[$ids],
                        'is_saveimg' => $flag,
                        'updatetime' => $currTime,
                        'createtime' => $currTime,
                    ];
                    $flag = 0;


//                  /*TODO：调试时用*/
                    if (count($newList) >= 10) break 2;
                }

            }
            halt($newList);

            $shopInfo = [
                'name' => $productName,
                'page_title' => $pageTitle,
                'meta_description' => $metaDescription,
                'store_url' => self::$host . $storeUrl,
                'shop_description' => $shopDescription,
                "shop_uniqid" => $params['shop_uniqid'],
            ];

            echo 2;
            return ['list' => $newList, 'shopInfo' => $shopInfo];
        } catch (Exception $e) {
            echo "Error: " . $e->getMessage();
        }
        return ['list' => [], 'shopInfo' => []];
    }

    private static function getPageDataByPreg($html)
    {
        // 定义标题正则表达式
        $titlePattern = "/<h1[^>]*class='h__h1--sm h--no-s-b'[^>]*>(.*?)<\/h1>/";
        preg_match($titlePattern, $html, $titleMatches);
        $productName = isset($titleMatches[1]) ? trim($titleMatches[1]) : '';

        // 定义title标签正则表达式
        $titlePattern = "/<title[^>]*>(.*?)<\/title>/";
        preg_match($titlePattern, $html, $titleMatches);
        $pageTitle = isset($titleMatches[1]) ? trim($titleMatches[1]) : '';

        // 定义meta正则表达式
        $pattern = "/<meta\s+content='([^']*)'\s+name='description'\s*\/?>/i";
        preg_match($pattern, $html, $matches);
        $metaDescription = isset($matches[1]) ? trim($matches[1]) : '';

        // 定义店铺链接正则表达式
        $pattern = '/<a[^>]*href="([^"]*)"[^>]*class="[^"]*link m-also-available-products--more-by-artist btn c-link__button tp-btn--medium link--default[^"]*"[^>]*>/';
        preg_match($pattern, $html, $matches);
        $storeUrl = isset($matches[1]) ? trim($matches[1]) : '';

        // 定义标题正则表达式
        $pattern = '/"description": "(.*?)",/s';
        preg_match($pattern, $html, $matches);
        $shopDescription = isset($matches[1]) ? trim($matches[1]) : '';

        // 定义商品json正则表达式
        $jsonPattern = "/TeePublic\['ProductOptions'\]\s*=\s*(\{.*?\});/s";
        preg_match($jsonPattern, $html, $jsonMatches);
        $productJson = isset($jsonMatches[1]) ? trim($jsonMatches[1]) : '';
        return [$productName, $pageTitle, $metaDescription, $storeUrl, $shopDescription, $productJson];
    }

    /**
     * 整理对应字段的数据内容
     * @param $products array 产品信息
     * @param $field string 获取的详细字段
     * @return array
     */
    private static function handleProductsInfo($products, $field)
    {
        switch ($field) {
            case 'material':
                //材料
                $ret = [];
                array_map(function ($value) use (&$ret) {
                    $materialTemp = array_fill_keys($value[1], trim($value[0]));
                    $ret = $ret + $materialTemp; //数组保留键值合并
                }, $products['descriptions']);
                break;
            case 'prices':
                $products = $products['products'];
                //价格
                $ret = $products['prices'];
                $ret = array_map(function ($priceKey) use ($ret) {
                    return $ret[$priceKey]['retail_price_usd'];
                }, $products['prices_keys']);
                break;
            case 'attrIdsOrder':
                $ret = $products['products'][$field];
                break;
            default:
                $products = $products['products'];
                $ret = [];
                if (!empty($products['attrs'][$field])) {
                    $ret = array_map(function ($color) {
                        return $color['name'];
                    }, $products['attrs'][$field]);
                }
        }
        return $ret;

        //性别
//        $gender = [];
//        if (!empty($products['attrs']['gender'])) {
//            $gender = array_map(function ($value) {
//                return $value['name'];
//            }, $products['attrs']['gender']);
//        }
//        //样式
//        $styles = array_map(function ($value) {
//            return $value['name'];
//        }, $products['attrs']['style']);
//        //尺码
//        $size = array_map(function ($value) {
//            return $value['name'];
//        }, $products['attrs']['size']);
//
//        return [$colors, $gender, $styles, $size, $prices, $material];
    }

    /**
     * 获取产品对应的图片
     * @param $productId int 商品产品ID
     * @return array|false
     */
    private static function getProductImages($productId, $getUrl)
    {
        $newList = [];
        try {
            $productImagesListJson = self::curlProductImages($productId, $getUrl);
            $productImagesList = json_decode($productImagesListJson, true);
            if (!isset($productImagesList['images'])) return false;

            foreach ($productImagesList['images'] as $image) {
                $tempImage = [
                    'images' => '',
                    'product_ids' => $image['product_ids'],
                ];
                //整理图片地址
                $imgs = [];
                foreach ($image['images'] as $img) {
                    $imgs[] = $img['url'];
                }
                $tempImage['images'] = implode("_____", $imgs);
                $newList[] = $tempImage;
            }
        } catch (GuzzleException $e) {
            echo "Error: " . $e->getMessage();
        }
        return $newList;
    }


    /**
     * curl获取产品在线图片JSON
     * @param $productId string 商品详情地址
     * @return string|null
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    private static function curlProductImages($productId, $getUrl)
    {
        $canvasIndex = self::getCanvasIndex($getUrl);
        $url = "https://www.teepublic.com/designs/{$productId}/canvas/{$canvasIndex}/product_images";
        return self::request($url);
    }

    private static function getCanvasIndex($url)
    {
        $canvasIndex = [
            ['name' => '/t-shirt/', 'index' => 1],
            ['name' => '/tank-top/', 'index' => 2],
            ['name' => '/hoodie/', 'index' => 4],
            ['name' => '/crewneck-sweatshirt/', 'index' => 5],
            ['name' => '/long-sleeve-t-shirt/', 'index' => 7],
            ['name' => '/hat/', 'index' => 55],
            ['name' => '/kids-t-shirt/', 'index' => 3],  //
            ['name' => '/kids-hoodie/', 'index' => 14],
            ['name' => '/kids-long-sleeve-t-shirt/', 'index' => 15],
            ['name' => '/poster-and-art/', 'index' => 6],
            ['name' => '/mug/', 'index' => 12],
            ['name' => '/throw-pillow/', 'index' => 17],
            ['name' => '/tote/', 'index' => 18],
            ['name' => '/tapestry/', 'index' => 19],
            ['name' => '/phone-case/', 'index' => 9],
        ];

        foreach ($canvasIndex as $temp) {
            if (str_contains($url, $temp['name'])) {
                return $temp['index'];
            }
        }
        return 1;
    }

    /**
     * 从URL中截取商品ID
     * @param $url
     * @return int
     */
    private static function getProductId($url)
    {
        $tempArr = explode('/', $url);
        return (int)end($tempArr);
    }

    /**
     * 请求数据
     * @param $url
     * @return string|void
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    private static function request($url)
    {
        try {
            // 创建Guzzle客户端
            $client = new Client([
                'verify' => false, // 禁用SSL验证
            ]);

            // 发送HTTP请求
            $response = $client->request('GET', $url, [
                'headers' => [
                    'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36',
                    'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
                    'Accept-Encoding' => 'gzip, deflate, br, zstd',
                    'Accept-Language' => 'zh-CN,zh;q=0.9',
                    'Referer' => 'https://www.teepublic.com',
                ]
            ]);

            // 获取HTML内容
            $html = $response->getBody()->getContents();
            return $html;
        } catch (Exception $exception) {
            echo "Error: " . $exception->getMessage();
        }
    }

    /**
     * 请求头
     * @return string[]
     */
    private static function buildHeaders()
    {
        return [
            'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36',
            'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
            'Accept-Encoding' => 'gzip, deflate, br, zstd',
            'Accept-Language' => 'zh-CN,zh;q=0.9',
            'Referer' => 'https://www.teepublic.com',
        ];
    }

}
